package com.shockweb.service;


import java.io.File;
import java.io.IOException;
import java.nio.channels.Selector;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.timeout.IdleStateHandler;
import com.shockweb.common.sitelocal.LANAddress;
import com.shockweb.common.utils.FileTools;
import com.shockweb.common.utils.PropertyFile;
import com.shockweb.client.ClientException;
import com.shockweb.client.impl.ConfigCenterClient;
import com.shockweb.common.log.LogManager;
import com.shockweb.register.RegisterServerException;
import com.shockweb.service.config.ServiceConfig;
import com.shockweb.service.data.ServiceStatus;

/**
 * 微服务服务器
 * 
 * @author 彭明华
 * 2018年1月3日 创建
 */
public class ServiceServer implements Runnable{

    /**
     * 当前服务器的url
     */
	private ServiceConfig config = null;

	/**
	 * 当前微中心实例
	 */
    private static ServiceServer server;
    
    /**
     * 返回当前服务器
     * @return
     */
    public static ServiceServer getServer(){
    	return server;
    }
    /**
     * {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
     *
     */
    private volatile ServerBootstrap b;

    
    /**
     * 获取当前微中心的配置
     * @return
     */
	public ServiceConfig getConfig() {
		return config;
	}
    
	/**
	 * 默认的启动函数，
	 * 指定配置文件的路径
	 * @param args
	 * @throws RegisterServerException
	 */
	public static void main(String[] args) throws ServiceServerException{
		if(args!=null && args.length==1){
			start(args[0]);
		}else if(args!=null && args.length==2){
			startConfigCenter(args[0],args[1]);
		}else{
			start(FileTools.getClassPath());
		}
	}
    
	/**
	 * 配置中心方式启动
	 * @param urls
	 * @param group
	 * @throws ServiceServerException
	 */
    public static void startConfigCenter(String urls,String group)throws ServiceServerException{
    	try{
	    	server = new ServiceServer();
	    	server.config = new ServiceConfig();
	    	if(urls!=null){
	    		server.config.setConfigCenterUrls(urls.replaceAll(" ", ""));
	    	}
	    	if(group!=null){
	    		server.config.setConfigCenterGroup(group.replaceAll(" ", ""));
	    	}
	    	if(server.config.getConfigCenterUrls()!=null){
	    		configCenterSetConfig(server.config.getConfigCenterUrls(),server.config.getConfigCenterGroup());
	    	}
	    	if(server.config.getPath()==null){
	    		server.config.setPath(FileTools.getClassPath());
	    	}
	    	InitManager.init(server.config);
	    	server.startUp();
    	}catch(ServiceServerException e){
    		throw e;
    	}catch(Exception e){
    		throw new ServiceServerException("启动微服务失败",e);
    	}
    }
	
	/**
	 * 启动微服务方法,配置文件方式启动
	 * @param propFilePath
	 * @throws IOException
	 */
    public static void start(String filePath)throws ServiceServerException{
    	try{
    		if(filePath.startsWith("classpath:")){
    			ClassLoader classLoader = null;
    			if(Thread.currentThread()!=null){
    				classLoader = Thread.currentThread().getContextClassLoader();
    			}else{
    				classLoader = ServiceServer.class.getClassLoader();
    			}
    			filePath = classLoader.getResource(filePath.substring("classpath:".length())).getPath();
    		}
	    	String file = null;
	    	if(new File(filePath).isFile()){
	    		file = filePath;
	    	}else if(new File(filePath).isDirectory()){
	    		file = FileTools.getFullPathFileName(filePath,"config.properties");
	    	}else{
	    		throw new ServiceServerException("配置文件路径" + filePath + "非法");
	    	}
	    	server = new ServiceServer();
	    	server.config = new ServiceConfig();
	    	Map<String,String> data = PropertyFile.read(file);
	    	if(data==null){
	    		throw new ServiceServerException("配置文件" + file + "内容为空");
	    	}
	    	
	    	if(data.get("service.configCenterUrls")!=null){
	    		server.config.setConfigCenterUrls(data.get("service.configCenterUrls").replaceAll(" ", ""));
	    	}
	    	if(data.get("service.configCenterGroup")!=null){
	    		server.config.setConfigCenterGroup(data.get("service.configCenterGroup").replaceAll(" ", ""));
	    	}
	    	if(server.config.getConfigCenterUrls()!=null){
	    		configCenterSetConfig(server.config.getConfigCenterUrls(),server.config.getConfigCenterGroup());
	    	}
	    	
	    	if(data.get("service.spaceName")!=null){
	    		server.config.setSpaceName(data.get("service.spaceName").replaceAll(" ", ""));
	    	}
	    	if(data.get("service.hostUrl")!=null){
	    		server.config.setHostUrl(data.get("service.hostUrl").replaceAll(" ", ""));
	    	}
	    	if(data.get("service.registerServerUrls")!=null){
	    		server.config.setRegisterServerUrls(data.get("service.registerServerUrls").replaceAll(" ", ""));
	    	}
	    	if(data.get("service.syncThreadSleepTime")!=null){
	    		server.config.setSyncThreadSleepTime(Integer.parseInt(data.get("service.syncThreadSleepTime").trim()));
	    	}
	    	if(data.get("service.serverIdleStateTime")!=null){
	    		server.config.setServerIdleStateTime(Integer.parseInt(data.get("service.serverIdleStateTime").trim()));
	    	}
	    	if(data.get("service.clientConnectTimeOut")!=null){
	    		server.config.setClientConnectTimeOut(Integer.parseInt(data.get("service.clientConnectTimeOut").trim()));
	    	}
	    	if(data.get("service.clientTimeOut")!=null){
	    		server.config.setClientTimeOut(Integer.parseInt(data.get("service.clientTimeOut").trim()));
	    	}
	    	if(data.get("service.clientSleepTime")!=null){
	    		server.config.setClientSleepTime(Integer.parseInt(data.get("service.clientSleepTime").trim()));
	    	}
	    	if(data.get("service.clientIdleStateTime")!=null){
	    		server.config.setClientIdleStateTime(Integer.parseInt(data.get("service.clientIdleStateTime").trim()));
	    	}
	    	if(data.get("service.doingThreshold")!=null){
	    		server.config.setDoingThreshold(Integer.parseInt(data.get("service.doingThreshold").trim()));
	    	}
	    	if(data.get("service.initializations")!=null){
	    		server.config.setInitializations(data.get("service.initializations").trim());
	    	}
	    	if(data.get("service.serviceTimeOut")!=null){
	    		server.config.setServiceTimeOut(Integer.parseInt(data.get("service.serviceTimeOut").trim()));
	    	}
	    	if(data.get("service.path")!=null){
	    		server.config.setPath(data.get("service.path").trim());
	    	}
	    	String path = null;
	    	if(new File(filePath).isFile()){
	    		path = new File(filePath).getPath();
	    		path = path.substring(0, path.lastIndexOf(File.separator));
	    	}else if(new File(filePath).isDirectory()){
	    		path = filePath;
	    	}
	    	if(server.config.getPath()==null){
	    		server.config.setPath(path);
	    	}
	    	InitManager.init(server.config);
	    	server.startUp();
    	}catch(ServiceServerException e){
    		throw e;
    	}catch(Exception e){
    		throw new ServiceServerException("启动微服务失败",e);
    	}
    }
    
    

    /**
     * 通过配置中心设置配置
     * @param urls
     * @param group
     */
    private static void configCenterSetConfig(String urls,String group)throws ClientException{
    	if(urls!=null){
    		ConfigCenterClient client = null;
    		try{
	    		client = ConfigCenterClient.getConfigCenterClient(server.config.getConfigCenterUrls());
	        	if(group==null){
	        		group = "service";
	        	}
	        	String spaceName = client.getConfig(group, "spaceName");
		    	if(spaceName!=null){
		    		server.config.setSpaceName(spaceName.replaceAll(" ", ""));
		    	}
		    	String hostUrl = client.getConfig(group, "hostUrl");
		    	if(hostUrl!=null){
		    		server.config.setHostUrl(hostUrl.replaceAll(" ", ""));
		    	}
		    	String registerServerUrls = client.getConfig(group, "registerServerUrls");
		    	if(registerServerUrls!=null){
		    		server.config.setRegisterServerUrls(registerServerUrls.replaceAll(" ", ""));
		    	}
		    	String syncThreadSleepTime = client.getConfig(group, "syncThreadSleepTime");
		    	if(syncThreadSleepTime!=null){
		    		server.config.setSyncThreadSleepTime(Integer.parseInt(syncThreadSleepTime.trim()));
		    	}
		    	String serverIdleStateTime = client.getConfig(group, "serverIdleStateTime");
		    	if(serverIdleStateTime!=null){
		    		server.config.setServerIdleStateTime(Integer.parseInt(serverIdleStateTime.trim()));
		    	}
		    	String clientConnectTimeOut = client.getConfig(group, "clientConnectTimeOut");
		    	if(clientConnectTimeOut!=null){
		    		server.config.setClientConnectTimeOut(Integer.parseInt(clientConnectTimeOut.trim()));
		    	}
		    	String clientTimeOut = client.getConfig(group, "clientTimeOut");
		    	if(clientTimeOut!=null){
		    		server.config.setClientTimeOut(Integer.parseInt(clientTimeOut.trim()));
		    	}
		    	String clientSleepTime = client.getConfig(group, "clientSleepTime");
		    	if(clientSleepTime!=null){
		    		server.config.setClientSleepTime(Integer.parseInt(clientSleepTime.trim()));
		    	}
		    	String clientIdleStateTime = client.getConfig(group, "clientIdleStateTime");
		    	if(clientIdleStateTime!=null){
		    		server.config.setClientIdleStateTime(Integer.parseInt(clientIdleStateTime.trim()));
		    	}
		    	String doingThreshold = client.getConfig(group, "doingThreshold");
		    	if(doingThreshold!=null){
		    		server.config.setDoingThreshold(Integer.parseInt(doingThreshold.trim()));
		    	}
		    	String initializations = client.getConfig(group, "initializations");
		    	if(initializations!=null){
		    		server.config.setInitializations(initializations.trim());
		    	}
		    	String serviceTimeOut = client.getConfig(group, "serviceTimeOut");
		    	if(serviceTimeOut!=null){
		    		server.config.setServiceTimeOut(Integer.parseInt(serviceTimeOut.trim()));
		    	}
		    	String path = client.getConfig(group, "path");
		    	if(path!=null){
		    		server.config.setPath(path.trim());
		    	}
	    	}finally{
	    		if(client!=null){
	    			client.close();
	    		}
	    	}
    	}
    }

    
	/**
	 * 启动微服务方法,完整参数
	 * @param config
	 * @throws IOException 
	 */
    public static void start(ServiceConfig config)throws ServiceServerException{
    	server = new ServiceServer();
    	server.config = config;
    	if(server.config.getPath()==null){
    		server.config.setPath(FileTools.getClassPath());
    	}
    	InitManager.init(server.config);
    	server.startUp();
    }
    
	/**
	 * 启动注册中心方法,默认网卡地址和3000端口
	 */
    public static void start()throws ServiceServerException{
    	server = new ServiceServer();
    	server.config = new ServiceConfig();
    	server.config.setHostUrl(LANAddress.getLocalHostLANAddress().getHostAddress() + ":3300");
    	server.startUp();
    }
    
    /**
     * 启动服务
     */
    private synchronized void startUp()throws ServiceServerException{
    	try{
			ServiceStatus.getServices().setSpaceName(config.getSpaceName());
	    	LogManager.infoLog(this.getClass(),"server=" + config.getHostUrl() + ",spaceName=" + config.getSpaceName());
	    	Thread thread = new Thread(this);
	    	thread.start();
	    	if(config.getRegisterServerUrls()!=null){
	    		SyncThread.start(config);
	    	}
    	}catch(ServiceServerException e){
    		stop();
    		throw e;
    	}
    }
    

	/**
	 * {@link MultithreadEventLoopGroup} implementations which is used for NIO {@link Selector} based {@link Channel}s.
	 */
    private volatile EventLoopGroup workerGroup;
    
    /**
     * {@link EventLoopGroup} 
     */
    private volatile EventLoopGroup bossGroup;
    
    /**
     * 异步启动方法
     */
    public void run() {
        // 一个线程组用于处理服务器接收客户端的连接
        // 另一个线程组用于处理SocketChannel的网络读写，用于网络事件的处理
        // EventLoopGroup就是用来管理调度他们的，微Channel，管理他们的生命周期
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup();
        server = this;
        try {
            // ServerBootstrap用于启动ServerChannel的，是服务端的工具类，Bootstrap是用于启动Channel，
            b = new ServerBootstrap();
            final EventLoopGroup workerGroup = this.workerGroup;
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
                            //当有操作操作超出指定空闲秒数时，便会触发UserEventTriggered事件
                            ch.pipeline().addLast(new IdleStateHandler(config.getServerIdleStateTime(), 0, 0, TimeUnit.MILLISECONDS));
                            ch.pipeline().addLast(workerGroup, new ServiceServerNettyHandler(config));// 业务处理
                            //ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
                        }
                    }).option(ChannelOption.SO_BACKLOG, 10000);// 配置TCP参数
            b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //通过NoDelay禁用Nagle,使消息立即发出去，不用等待到一定的数据量才发出去
            b.option(ChannelOption.TCP_NODELAY, true);
            b.childOption(ChannelOption.SO_KEEPALIVE, false); // 不保持常连接状态
            b.option(ChannelOption.SO_TIMEOUT, 5000);
            b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
            
            
            
            doBind();
        } catch (Exception e) {
            LogManager.errorLog(this.getClass(),"启动微服务器失败 Server=" + config.getHostUrl(),e);
        } finally {
            // 优雅退出 释放线程池资源
        	SyncThread.stop();
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            LogManager.infoLog(this.getClass(), "微服务服务器停止 Server=" + config.getHostUrl());
        }
    }

    /**
     * 启动并绑定服务端口
     */
    protected void doBind() {
        ChannelFuture f;
        try {
        	String[] tmp = config.getHostUrl()!=null?config.getHostUrl().split(":"):null;
        	String host = LANAddress.getLocalHostLANAddress().getHostAddress();
        	int port = 3300;
        	if(tmp!=null){
            	host = tmp[0];
            	if(tmp.length>1){
            		port = Integer.parseInt(tmp[1]);
            	}
        	}
            f = b.bind(host, port).sync();
            f.addListener(new ChannelFutureListener() {
                public void operationComplete(final ChannelFuture f)throws Exception {
                    if (f.isSuccess()) {
                        LogManager.infoLog(this.getClass(),"微服务器启动成功  Server=" + config.getHostUrl());
                    }else{
                    	LogManager.infoLog(this.getClass(), "微服务器启动失败 Server=" + f.cause().getMessage());
                    }
                }
            });
            f.channel().closeFuture().sync();
        } catch (Exception e) {
        	LogManager.errorLog(this.getClass(),"启动微服务器失败 Server=" + config.getHostUrl(),e);
        }
    }

	/**
	 * 停止服务
	 */
    public synchronized static void stop() {
    	try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
    	if(server.workerGroup!=null){
    		server.workerGroup.shutdownGracefully();
    	}
    	if(server.bossGroup!=null){
    		server.bossGroup.shutdownGracefully();
    	}

        
	}
    
}
